#pragma once 

#include <unistd.h>
#include <stdlib.h>
#include <iostream>
#include <sys/types.h>
#include <sys/sendfile.h>
#include <sys/stat.h>
#include <sys/socket.h>
#include <string>
#include <vector>
#include <sstream>
#include <cstdlib>
#include <algorithm>
#include <unordered_map>
#include <fcntl.h>
#include <sys/wait.h>
#include "Util.hpp"
#include "log.hpp"


#define SEP ": "
#define WEB_ROOT "wwwroot"
#define HOME_PAGE "index.html"
#define PAGE_404  "wwwroot/404.html"
#define HTTP_VERSION "HTTP/1.0"
#define LINE_END "\r\n"


#define OK 200
#define NOT_FOUND 404


static std::string Code2Desc(int code)
{
  std::string desc;
  switch(code)
  {
    case 200:
      desc = "OK";
      break;
    case 404:
      desc="Not Found";
      break;
    default:
      break;
  }
  return desc;
}

static std::string Suffix2Desc(const std::string& suffix) 
{
  static std::unordered_map<std::string , std::string> suffix2desc ={
    {".html" , "text/html"},
    {".css" , "text/css"},
    {".jpg" , "text/html"}
  };
  
  auto iter = suffix2desc.find(suffix);
  if (iter != suffix2desc.end())
  {
    return iter->second;
  }

  return "text/html";
}

class HttpRequest{
  public:
    std::string _request_line;
    std::vector<std::string> _request_header;
    std::string _blank;
    std::string _request_body;

    // prase 
    std::string method;
    std::string uri;
    std::string version;

    std::unordered_map<std::string , std::string> header_kv;
    int content_length;
    std::string path;
    std::string suffix; 
    std::string query_string;


    // cgi 
    bool cgi;
  public:
    HttpRequest()
      :content_length(0),
      cgi(false)
      {}
    ~HttpRequest()
    {}
};

class HttpResponse{
  public:
    std::string _response_line;
    std::vector<std::string> _response_header;
    std::string _blank;
    std::string _response_body;

    int response_code; 
    int fd;
    int size;
    HttpResponse()
      :_blank(LINE_END),
      response_code(200),
      fd(-1)
    {}
    ~HttpResponse()
    {}
};

class EndPoint{
  private:
    int _sock;
    bool stop;
    HttpRequest http_request;
    HttpResponse http_response;
  private:
    bool RecvRequestLine()
    {
      if(Util::ReadLine(_sock ,http_request._request_line) >0 )
      {
        std::cout << http_request._request_line ;
      }
      else 
      {
        stop = true;
      }
      return stop;
    }
    bool RecvRequestHeader()
    {
      std::string line;
      while(true)
      {
        line.clear();
        if (Util:: ReadLine(_sock , line) <= 0)
        {
          stop = true;
        }
        if (line == "\n")
        {
          break;
        }
        line.resize(line.size()-1); // remove \n
        http_request._request_header.push_back(line);
      }


      if (line == "\n")
      {
        http_request._blank = line;
      }
        return stop;
    }
    void ParseHttpRequestLine()
    {
      auto& line = http_request._request_line;
      std::stringstream ss(line);
      ss >> http_request.method >> http_request.uri >> http_request.version;
      auto& method = http_request.method;
      std::transform(method.begin() , method.end() , method.begin() , ::toupper );
    }
    void ParseHttpRequestHeader()
    {
      std::string key;
      std::string value;
      for (auto& iter : http_request._request_header)
      {
        Util::CutString(iter , key , value , SEP);
        http_request.header_kv.insert({key,value});
      }
    }
    bool IsRecvBody()
    {
      auto method = http_request.method;
      if (method == "POST")
      {
        auto &header_kv = http_request.header_kv;
        auto iter = header_kv.find("Content-Length");
        if (iter != header_kv.end())
        {
          http_request.content_length = atoi(iter->second.c_str()); 
          return true;
        }
      }
      return false;
    }

    bool RecvRequestBody()
    {
      if (IsRecvBody())
      {
        int content_length = http_request.content_length;
        auto& body = http_request._request_body;

        char ch = 0;
        while(content_length)
        {
          ssize_t s = recv(_sock , &ch , 1, 0);
          if (s > 0)
          {
            body.push_back(ch);
            content_length--;
          }
          else 
          {
            stop = true;
            break;
          }
        }
      }

      return stop;
    }
    int ProcessNonCgi(int size)
    {
      http_response.fd = open(http_request.path.c_str() , O_RDONLY);
      if (http_response.fd >= 0)
      {
      http_response._response_line = HTTP_VERSION;
      http_response._response_line += " ";
      http_response._response_line += std::to_string(http_response.response_code);
      http_response._response_line += " ";
      http_response._response_line += Code2Desc(http_response.response_code);
      http_response._response_line += LINE_END;
      http_response.size = size;


      std::string header_line = "Content-Length: ";
      header_line += std::to_string(size);
      header_line += LINE_END;
      http_response._response_header.push_back(header_line);
    
      header_line += "Content-Type: ";
      header_line += Suffix2Desc(http_request.suffix);
      header_line += LINE_END;
      http_response._response_header.push_back(header_line);
      return OK; 
      }
      return 404;
    }

    void BuildOKResponse()
    {
        std::string line = "Contest-Type: ";
        line += Suffix2Desc(http_request.suffix);
        line += LINE_END;
        http_response._response_header.push_back(line);


        line = "Content-Length: ";
        if (http_request.cgi)
        {
          line += std::to_string(http_response._response_body.size());
        }
        else 
        {
          line += std::to_string(http_response.size);
        }
        line += LINE_END;
        http_response._response_header.push_back(line);
        
    }

    int ProcessCgi()
    {
      auto& method = http_request.method; 
      auto& query_string = http_request.query_string; // GET
      auto& body_text = http_request._request_body;  //POST 
      auto& bin = http_request.path; 
      auto& response_body = http_response._response_body;
      auto& code = http_response.response_code;
      int content_length = http_request.content_length;


      std::string content_length_env;
      std::string query_string_env;
      std::string method_env;

      int input[2];
      int output[2]; 

      if (pipe(input) < 0)
      {   
        return 404;
      }
      if (pipe(output) < 0)
      {
        return 404;
      }

      pid_t pid = fork();
      if (pid == 0)
      {
        // child
        close(input[0]);
        close(output[1]); 

        dup2(input[1] , 1);
        dup2(output[0], 0);

        method_env = "METHOD=";
        method_env += method;
        putenv((char *)method_env.c_str());

        if (method == "GET")
        {
          query_string_env = "QUERY_STRING=";
          query_string_env += query_string;
          putenv((char *)query_string_env.c_str());
        }
        else if (method == "POST")
        {
          content_length_env = "CONTENT_LENGTH=";
          content_length_env += std::to_string(content_length);
          putenv((char*)content_length_env.c_str());
        }
        else 
        {
          ;
        }


        execl(bin.c_str() , bin.c_str() , nullptr);
        exit(1);
      }
      else if (pid < 0)
      {
        // error
        LOG(ERROR , "fork error");
        return 404;
      }
      else 
      {
        // father
        close(input[1]);
        close(output[0]);

        if (method == "POST")

        {
          const char* start = body_text.c_str();
          int total = 0;
          int size = 0;
          while(1)
          {
            size = write(output[1] , start+total , body_text.size()-total);
            if (size > 0)
            {
              total+=size;
            }
            else 
            {
              break;
            }
          }
        }

        char ch = 0;
        while(read(input[0] , &ch , 1))
        {
          response_body.push_back(ch);
        }

        code = 200;
        waitpid(pid , nullptr , 0);

        close(input[0]);
        close(output[1]);
      }
      return code;
    }
    void HandlerNotFound()
    {
      //  return 404 html
      http_response.fd = open(PAGE_404 , O_RDONLY);
      if (http_response.fd > 0)
      {
        struct stat st;
        stat(PAGE_404 , &st);
        std::string line = "Contest-Type: text/html";
        line += LINE_END;
        http_response._response_header.push_back(line);
        line = "Content-Length: ";
        line += std::to_string(st.st_size);
        line += LINE_END;
        http_response._response_header.push_back(line);
        http_response.size = st.st_size;
      }
    }
    void BuildHttpResponseHelper()
    {
      // http_request; 
      // http_response;
      auto& code = http_response.response_code;

      // build status_line  
      auto& status_line = http_response._response_line;
      status_line += HTTP_VERSION;
      status_line += " ";
      status_line += std::to_string(code);
      status_line += " ";
      status_line += Code2Desc(code);
      status_line += LINE_END;

      // build response body  maybe have response line 
      switch(code)
      {
        case 200:
          BuildOKResponse();
          break;
        case 404:
          HandlerNotFound();
       //  case 505:
       //    xxx
          break;
        default:
          break;
      }
    }
  public:
    EndPoint(int sock)
      :_sock(sock),
      stop(false)
    {}

    bool Stop()
    {
      return this->stop;
    }
    void RcvRequest()
    {
    if(  RecvRequestLine() ||RecvRequestHeader() )
    {
      // error 
      ;
    }
    else 
    {
      ParseHttpRequestLine();
      ParseHttpRequestHeader();
      RecvRequestBody();
    }

    }
    void BuildResponse()
    {
      std::string _path = http_request.path;
      auto& code = http_response.response_code;
      struct stat st;
      int size = 0;
      std::size_t found = 0;
      if (http_request.method != "GET" && http_request.method != "POST")
      {
        // waring request 
        code = NOT_FOUND;
        goto END;
      }
      if (http_request.method == "GET")
      {
        size_t pos = http_request.uri.find("?");
        if (pos != std::string::npos)
        {
          Util::CutString(http_request.uri,http_request.path, http_request.query_string , "?");
          http_request.cgi = true; 
        }
        else 
        {
          http_request.path = http_request.uri;
        }
      }
      else if (http_request.method == "POST")
      {
        // post 
        http_request.cgi = true;
        http_request.path = http_request.uri;
      }
      _path = http_request.path;
      // std::cout << "debug url: " << http_request.uri << std::endl;
      // std::cout << "debug path: " << http_request.path << std::endl;
      // std::cout << "debug query_string: " << http_request.query_string << std::endl;
      http_request.path = WEB_ROOT;
      http_request.path += _path;

      if (http_request.path[http_request.path.size()-1] == '/')
      {
        http_request.path += HOME_PAGE;
      }

      if (stat(http_request.path.c_str() , &st) == 0)
      {
        // exist
        if (S_ISDIR(st.st_mode))
        {
          // is idr 
          http_request.path += "/";
          http_request.path += HOME_PAGE;
          stat(http_request.path.c_str(),&st);
        }
        if ((st.st_mode&S_IXUSR) || (st.st_mode&S_IXGRP) || (st.st_mode&S_IXOTH))
        {
          // special treatment 
          http_request.cgi = true; 
        }
        size = st.st_size;
      }
      else 
      {
        // not exist 
        code = NOT_FOUND;
        goto END;
      }

      found = http_request.path.rfind(".");
      if (found == std::string::npos)
      {
        http_request.suffix = ".html";
      }
      else 
      {
        http_request.suffix = http_request.path.substr(found);
      }

      if (http_request.cgi)
      {
        code =  ProcessCgi();
      }
      else 
      {
        code = ProcessNonCgi(size);
      }


      
END:
     BuildHttpResponseHelper();
    }
    void SendResponse()
    {
      
      write(_sock ,http_response._response_line.c_str() , http_response._response_line.size());
      for(auto it : http_response._response_header)
      {
        write(_sock, it.c_str() , it.size());
      }
        write(_sock,http_response._blank.c_str() , http_response._blank.size());
        if (http_request.cgi)
        {
          auto& response_body = http_response._response_body;
          int size = 0;
          int total = 0;
          const char* start = response_body.c_str();
          while( (size =  write(_sock , start+total , response_body.size()) - total)> 0 )
          {
            total += size;
          }
        }
        else 
        {
           sendfile(_sock , http_response.fd , nullptr , http_response.size);
           close(http_response.fd);

         }
    }
    ~EndPoint()
    {
      close(_sock);
    }
};


class CallBack
{
  public:
    CallBack()
    {}

    void operator()(int sock)
    {
      HandlerRequest(sock);
    }
    void HandlerRequest(int sock)
    {
      LOG(INFO , "Handler Request Begin!");
#ifdef DEBUG 
          char buffer[4096];
          recv(sock , buffer , sizeof(buffer) , 0); 
          std::cout << "--------------begin-------------" << std::endl;
          std::cout << buffer << std::endl;
          std::cout << "-------------- end -------------" << std::endl;
#else
          EndPoint* ep = new EndPoint(sock);
          ep->RcvRequest();
          if (!ep->Stop())
          {
            ep->BuildResponse();
            ep->SendResponse();
          }
          delete ep;
#endif
          LOG(INFO , "Hander Request End");
    }

    ~CallBack()
    {}
};
